#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "esp_system.h"
#include "esp_log.h"
#include "esp_console.h"
#include "esp_vfs_dev.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "driver/uart.h"
#include "driver/spi_master.h"
#include "linenoise/linenoise.h"
#include "argtable3/argtable3.h"
#include "cmd_system.h"

#define PROMPT_STR "SPI_Debugger v0.9.0"
static const char* TAG = PROMPT_STR;
static xQueueHandle pd_queue = NULL;
uint8_t enable;
spi_device_handle_t spi;
extern cmd_t pll_user_cmds[script_len+1];
static void IRAM_ATTR pd_isr_handler(void* arg)
{
    uint32_t gpio_num = (uint32_t) arg;
    xQueueSendFromISR(pd_queue, &gpio_num, NULL);
}

static void powerdown_task(void* arg)
{
    uint32_t io_num;
    for(;;) {
        if(xQueueReceive(pd_queue, &io_num, portMAX_DELAY)) {
            if (io_num == PIN_NUM_PD){
                uint8_t t=gpio_get_level(PIN_NUM_PD);
                if (t && (!enable)){
                    esp_err_t ret;
                    gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT);
                    gpio_set_level(PIN_NUM_RST, 1);
                    spi_bus_config_t buscfg={
                        .miso_io_num=PIN_NUM_MISO,
                        .mosi_io_num=PIN_NUM_MOSI,
                        .sclk_io_num=PIN_NUM_CLK,
                        .quadwp_io_num=-1,
                        .quadhd_io_num=-1
                    };
                    spi_device_interface_config_t devcfg={
                        .clock_speed_hz=1000,
                        .mode=0,
                        .spics_io_num=PIN_NUM_CS,
                        .queue_size=7,
                        .command_bits=2,
                        .address_bits=6,
                    };
                    ret=spi_bus_initialize(PLL_HOST, &buscfg, 0);
                    ESP_ERROR_CHECK(ret);
                    ret=spi_bus_add_device(PLL_HOST, &devcfg, &spi);
                    enable = 1;
                    ESP_LOGW(TAG, "Enabled");
                } else if (enable && (!t)){
                    spi_bus_remove_device(spi);
                    spi_bus_free(PLL_HOST);
                    gpio_set_pull_mode(PIN_NUM_RST, GPIO_FLOATING);
                    gpio_set_pull_mode(PIN_NUM_CS, GPIO_FLOATING);
                    gpio_set_pull_mode(PIN_NUM_CLK, GPIO_FLOATING);
                    gpio_set_pull_mode(PIN_NUM_MOSI, GPIO_FLOATING);
                    gpio_set_pull_mode(PIN_NUM_MISO, GPIO_FLOATING);
                    gpio_set_level(PIN_NUM_RST, 0);
                    gpio_set_level(PIN_NUM_CS, 0);
                    gpio_set_level(PIN_NUM_CLK, 0);
                    gpio_set_level(PIN_NUM_MOSI, 0);
                    gpio_set_level(PIN_NUM_MISO, 0);
                    gpio_set_direction(PIN_NUM_RST, GPIO_MODE_DISABLE);
                    gpio_set_direction(PIN_NUM_CS, GPIO_MODE_DISABLE);
                    gpio_set_direction(PIN_NUM_CLK, GPIO_MODE_DISABLE);
                    gpio_set_direction(PIN_NUM_MOSI, GPIO_MODE_DISABLE);
                    gpio_set_direction(PIN_NUM_MISO, GPIO_MODE_DISABLE);
                    enable = 0;
                    ESP_LOGW(TAG, "Disabled");
                } 
            }
        }
    }
}

static void initialize_system(void){
    gpio_set_direction(PIN_NUM_PD, GPIO_MODE_INPUT);
    enable=gpio_get_level(PIN_NUM_PD);
    if (enable) {
        esp_err_t ret;
        gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT);
        gpio_set_level(PIN_NUM_RST, 1);
        spi_bus_config_t buscfg={
            .miso_io_num=PIN_NUM_MISO,
            .mosi_io_num=PIN_NUM_MOSI,
            .sclk_io_num=PIN_NUM_CLK,
            .quadwp_io_num=-1,
            .quadhd_io_num=-1
        };
        spi_device_interface_config_t devcfg={
            .clock_speed_hz=1000,
            .mode=0,
            .spics_io_num=PIN_NUM_CS,
            .queue_size=7,
            .command_bits=2,
            .address_bits=6,
        };
        ret=spi_bus_initialize(PLL_HOST, &buscfg, 0);
        ESP_ERROR_CHECK(ret);
        ret=spi_bus_add_device(PLL_HOST, &devcfg, &spi);
        ESP_ERROR_CHECK(ret);
        ESP_LOGW(TAG, "Enabled");
    } else {
        gpio_set_pull_mode(PIN_NUM_RST, GPIO_FLOATING);
        gpio_set_pull_mode(PIN_NUM_CS, GPIO_FLOATING);
        gpio_set_pull_mode(PIN_NUM_CLK, GPIO_FLOATING);
        gpio_set_pull_mode(PIN_NUM_MOSI, GPIO_FLOATING);
        gpio_set_pull_mode(PIN_NUM_MISO, GPIO_FLOATING);
        gpio_set_level(PIN_NUM_RST, 0);
        gpio_set_level(PIN_NUM_CS, 0);
        gpio_set_level(PIN_NUM_CLK, 0);
        gpio_set_level(PIN_NUM_MOSI, 0);
        gpio_set_level(PIN_NUM_MISO, 0);
        gpio_set_direction(PIN_NUM_RST, GPIO_MODE_DISABLE);
        gpio_set_direction(PIN_NUM_CS, GPIO_MODE_DISABLE);
        gpio_set_direction(PIN_NUM_CLK, GPIO_MODE_DISABLE);
        gpio_set_direction(PIN_NUM_MOSI, GPIO_MODE_DISABLE);
        gpio_set_direction(PIN_NUM_MISO, GPIO_MODE_DISABLE);
        ESP_LOGW(TAG, "Disabled");
    }
    pd_queue = xQueueCreate(10, sizeof(uint32_t));
    pll_user_cmds[script_len].hook = 0xff;
    xTaskCreate(powerdown_task, "powerdown_task", 16384, NULL, 1, NULL);
    gpio_config_t io_conf;
    io_conf.intr_type = GPIO_INTR_ANYEDGE;
    io_conf.mode = GPIO_MODE_INPUT;
    io_conf.pin_bit_mask = (1ULL<<PIN_NUM_PD);
    gpio_config(&io_conf);
    gpio_install_isr_service(0);
    gpio_isr_handler_add(PIN_NUM_PD, pd_isr_handler, (void*) PIN_NUM_PD);
    ESP_LOGI(TAG, "Successfully init system");
}

static void initialize_console(void)
{
    /* Drain stdout before reconfiguring it */
    fflush(stdout);
    fsync(fileno(stdout));
    /* Disable buffering on stdin */
    setvbuf(stdin, NULL, _IONBF, 0);
    /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
    esp_vfs_dev_uart_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR);
    /* Move the caret to the beginning of the next line on '\n' */
    esp_vfs_dev_uart_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF);
    /* Configure UART. Note that REF_TICK is used so that the baud rate remains
     * correct while APB frequency is changing in light sleep mode.
     */
    const uart_config_t uart_config = {
            .baud_rate = CONFIG_ESP_CONSOLE_UART_BAUDRATE,
            .data_bits = UART_DATA_8_BITS,
            .parity = UART_PARITY_DISABLE,
            .stop_bits = UART_STOP_BITS_1,
            .source_clk = UART_SCLK_REF_TICK,
    };
    /* Install UART driver for interrupt-driven reads and writes */
    ESP_ERROR_CHECK(uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0));
    ESP_ERROR_CHECK(uart_param_config(CONFIG_ESP_CONSOLE_UART_NUM, &uart_config));
    /* Tell VFS to use UART driver */
    esp_vfs_dev_uart_use_driver(CONFIG_ESP_CONSOLE_UART_NUM);
    /* Initialize the console */
    esp_console_config_t console_config = {
            .max_cmdline_args = 16,
            .max_cmdline_length = 256,
#if CONFIG_LOG_COLORS
            .hint_color = atoi(LOG_COLOR_CYAN)
#endif
    };
    ESP_ERROR_CHECK( esp_console_init(&console_config) );
    /* Configure linenoise line completion library */
    /* Enable multiline editing. If not set, long commands will scroll within
     * single line.
     */
    linenoiseSetMultiLine(1);
    /* Tell linenoise where to get command completions and hints */
    linenoiseSetCompletionCallback(&esp_console_get_completion);
    linenoiseSetHintsCallback((linenoiseHintsCallback*) &esp_console_get_hint);
    /* Set command history size */
    linenoiseHistorySetMaxLen(100);
    /* Don't return empty lines */
    linenoiseAllowEmpty(false);
}

void app_main(void)
{
    initialize_system();
    initialize_console();
    esp_console_register_help_command();
    register_system();

    char* prompt = LOG_COLOR_I PROMPT_STR "> " LOG_RESET_COLOR;
    printf("\n"
           "This is a SPI Debugger console.\n"
           "Type 'help' to get the list of commands.\n"
           "Use UP/DOWN arrows to navigate through command history.\n"
           "Press TAB when typing command name to auto-complete.\n"
           "Press Enter or Ctrl+C will terminate the console environment.\n");

    /* Figure out if the terminal supports escape sequences */
    int probe_status = linenoiseProbe();
    if (probe_status) { /* zero indicates success */
        printf("\n"
               "Your terminal application does not support escape sequences.\n"
               "Line editing and history features are disabled.\n"
               "On Windows, try using Putty instead.\n");
        linenoiseSetDumbMode(1);
    #if CONFIG_LOG_COLORS
            /* Since the terminal doesn't support escape sequences,
            * don't use color codes in the prompt.
            */
            prompt = PROMPT_STR "> ";
    #endif //CONFIG_LOG_COLORS
    }

    /* Main loop */
    while(true) {
        /* Get a line using linenoise.
         * The line is returned when ENTER is pressed.
         */
        char* line = linenoise(prompt);
        if (line == NULL) { /* Break on EOF or error */
            linenoiseFree(line);
            continue;
        }
        /* Add the command to the history if not empty*/
        if (strlen(line) > 0) {
            linenoiseHistoryAdd(line);
        }

        /* Try to run the command */
        int ret;
        esp_err_t err = esp_console_run(line, &ret);
        if (err == ESP_ERR_NOT_FOUND) {
            printf("Unrecognized command\n");
        } else if (err == ESP_ERR_INVALID_ARG) {
            // command was empty
        } else if (err == ESP_OK && ret != ESP_OK) {
            printf("Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(ret));
        } else if (err != ESP_OK) {
            printf("Internal error: %s\n", esp_err_to_name(err));
        }
        /* linenoise allocates line buffer on the heap, so need to free it */
        linenoiseFree(line);
    }
    ESP_LOGE(TAG, "Error or end-of-input, terminating console");
    esp_console_deinit();
}
